AWS アップデートの予兆ないかな? 直近で更新された AWS サービス用 IAM ポリシーを一覧取得したのち変更差分を眺めてニヤニヤしてみた
コンバンハ、千葉(幸)です。
今回やりたいのはこういったことです。
- 過去 N 日間のうちに変更が加えられた 管理ポリシーをリスト化する
- その中から任意のポリシーを選択し、変更前と変更後の内容を取得する
- 差分の内訳を眺めてニヤニヤする
皆さんも定期的にこういった行為に及びたくなるかと思います。
私もいざ実行しようとしたのですが、手順を確立できていなかったため、ニヤニヤするまでに時間がかかってしまいました。
今後の迅速なニヤニヤ実現のため、手順を整備しておきます。
先に手順のまとめ
詳細は追って説明しますが、先に手順だけ書いておきます。
0. 前提
この手順においては、以下の AWS CLI コマンドを使用します。必要な権限を有していることを前提とします。
- aws iam list-policies
- aws iam get-policy-version
(あと jq も使用します)
以降の手順では、変数の設定とコマンドの実行を一連の流れとして実行していきます。
1. フィルタリング条件の指定
まずはポリシーのリスト化をする際のフィルタリング条件を指定します。(遡る期間、プレフィックス、ポリシー ARN のキーワード)
AGO=30 DATE=`date -v-"$AGO"d +%Y-%m-%d` PREFIX=/aws-service-role/ WORD=
2. ポリシーの一覧の取得
ポリシーの一覧を取得し、変数に格納。加えてその内容を出力します。
RESULT=`aws iam list-policies\ --scope AWS\ --path-prefix $PREFIX\ --query "Policies[? UpdateDate >= '$DATE' && contains(Arn, '$WORD')].{name:PolicyName,version:DefaultVersionId,arn:Arn,date:UpdateDate}"\ | jq 'to_entries | map({index: .key, value: .value})'` echo $RESULT
ここで出力されたポリシーの一覧にはインデックス(0,1,2,...)が付与されています。
3. 特定のポリシーの前後のバージョン取得
詳細を確認したいポリシーのインデックスを指定し、直前のバージョンと最新のバージョンの内訳を変数に格納します。
INDEX=3 ARN=`echo $RESULT | jq -r '.['$INDEX'].value.arn'` VER=`echo $RESULT | jq -r '.['$INDEX'].value.version' | tr -d "v"` BEFORE=`aws iam get-policy-version --policy-arn $ARN --version-id v$((VER-1)) --query PolicyVersion.Document` AFTER=`aws iam get-policy-version --policy-arn $ARN --version-id v$VER --query PolicyVersion.Document`
4. diff る
お好みの diff でニヤニヤします。(周りに人がいない環境での実行を推奨。)
diff <(echo $BEFORE) <(echo $AFTER) diff <(echo $BEFORE) <(echo $AFTER) -u diff <(echo $BEFORE) <(echo $AFTER) -c diff <(echo $BEFORE) <(echo $AFTER) -y --left-column -W 190
ありがとうございます。
なぜそんなことをしたいのか
ここまで読まれて、なぜニヤニヤしたいのか意図がわからず、不安に感じられた方がいるかもしれません。
私がこういった行為に及ぼうと思ったのは、以下のエントリを書いたことがきっかけでした。
アップデートの前に管理ポリシーに変更が加わっていることがある
2021年1月のアップデートで、IAM アクセスアナライザーによる分析対象に Secrets Manager シークレットが追加されました。
アクセスアナライザーによる分析はarn:aws:iam::aws:policy/aws-service-role/AccessAnalyzerServiceRolePolicy
という AWS 管理ポリシーを使用して行われるのですが、実は 2020年11月の時点でこのポリシーに変更が加わっていたのです。
以下が変更差分です。許可されるアクションの中に、secretsmanager:
に関するものが追加されています。
"secretsmanager:DescribeSecret", "secretsmanager:GetResourcePolicy", "secretsmanager:ListSecrets",
つまり、2020年11月の時点でこのポリシーの変更に気づけていれば、将来的に行われるアップデートの予測を立てられた可能性があります。こういった部分にアンテナを張っておきたい、というのが今回の主旨です。
もちろんすべてのアップデートの前触れとして管理ポリシーの変更が行われるわけではありませんが、それはそうとして差分を確認するのは楽しいですよね。そうですよね。
手順について補足
冒頭で述べた手順について、詳細を補足していきます。
1. フィルタリング条件の指定
後続の2.の手順でaws iam list-policies
を実行する際の、オプションとして指定する値を定義します。
# | 変数名 | 値の指定 | 概要 |
---|---|---|---|
1 | AGO | 要 | 日数を指定。#2の中で使用します |
2 | DATE | 不要 | AGO日前の日付を YYYY-MM-DD の形式で指定 |
3 | PREFIX | 要 | IAMポリシーのプレフィックスを指定 |
4 | WORD | 任意 | ポリシー名に含まれるワードを指定(EC2など)。ブランク可 |
AGO=30 DATE=`date -v-"$AGO"d +%Y-%m-%d` PREFIX=/aws-service-role/ WORD=
#3 で指定するものとして想定しているのは以下です。
/aws-service-role/
:AWSサービスリンクロールが使用するポリシーのみフィルタリングしたい時/service-role/
:AWSサービスロールが使用するポリシーのみフィルタリングしたい時/
:AWS 管理ポリシーすべてを対象としたい時
ざっくり、AWS サービスリンクロールは特定の AWS サービス全体(例えば ELB 用、AWS Client VPN 用など)が使用するもの、AWS サービスロールは Lambda 関数や RDS インスタンスなど個々の AWS リソースに割り当てるもの、と捉えてください。
RDS を例にとったエントリを以前書いたので興味があればご参照ください。
2. ポリシーの一覧の取得
ポリシー一覧を取得します。結果を後続の手順で使用するため、一度変数RESULT
に格納した上で、その内容を出力します。
RESULT=`aws iam list-policies\ --scope AWS\ --path-prefix $PREFIX\ --query "Policies[? UpdateDate >= '$DATE' && contains(Arn, '$WORD')].{name:PolicyName,version:DefaultVersionId,arn:Arn,date:UpdateDate}"\ | jq 'to_entries | map({index: .key, value: .value})'` echo $RESULT
オプションについて補足します。
--scope オプション
以下のいずれかを指定可能です。
All
: AWS 管理ポリシーおよびカスタマー管理ポリシーAWS
: AWS 管理ポリシーLocal
: カスタマー管理ポリシー
ここでは AWS 管理ポリシーのフィルタリングを想定していますが、お好みに応じて変更してください。
--path-prefix オプション
1.の手順で #3 として指定したPREFIX
がここで適用されます。再掲すると以下のような値を想定しています。
/aws-service-role/
:AWSサービスリンクロールが使用するポリシーのみフィルタリングしたい時/service-role/
:AWSサービスロールが使用するポリシーのみフィルタリングしたい時/
:AWS 管理ポリシーすべてを対象としたい時
ここでは*
が使用できないので、*service-role/
( AWS サービスリンクロールかAWSサービスロールが使用するポリシー)といった指定はできません。
そのような指定をしたい場合は、#4 のWORD
に含めることで実現できます。
--query オプションで複数条件を指定(日時、キーワード)
--query では以下のような指定をしています。
--query "Policies[? UpdateDate >= '$DATE' && contains(Arn, '$WORD')].{name:PolicyName,version:DefaultVersionId,arn:Arn,date:UpdateDate}"
前半のPolicies[? UpdateDate >= '$DATE' && contains(Arn, '$WORD')]
の部分で、以下の条件に合致するポリシーのフィルタリングをしています。
- ポリシーの更新日時が
DATE
より新しい(か同一である) - かつ
- ポリシーARNに
WORD
を含む
条件を追加したければ&&
で繋ぐことで実現できます。
そして、後半の{name:PolicyName,version:DefaultVersionId,arn:Arn,date:UpdateDate}
の部分で、出力結果のフィルタリングをしています。
デフォルトでは、一つのポリシーにつき以下の情報が出力されます。
{ "PolicyName": "AdministratorAccess", "CreateDate": "2015-02-06T18:39:46Z", "AttachmentCount": 5, "IsAttachable": true, "PolicyId": "ANPAIWMBCKSKIEE64ZLYK", "DefaultVersionId": "v1", "Path": "/", "Arn": "arn:aws:iam::aws:policy/AdministratorAccess", "UpdateDate": "2015-02-06T18:39:46Z" },
フィルタリングにより、以下のような出力結果となります。
{ "name": "AdministratorAccess", "version": "v1", "arn": "arn:aws:iam::aws:policy/AdministratorAccess", "date": "2015-02-06T18:39:46Z" },
jq によるインデックスの追加
jq 'to_entries | map({index: .key, value: .value})'
により、出力結果のポリシーに番号を付与しています。
番号追加前の出力イメージが以下。
[ { "name": "AWSStorageGatewayServiceRolePolicy", "version": "v1", "arn": "arn:aws:iam::aws:policy/aws-service-role/AWSStorageGatewayServiceRolePolicy", "date": "2021-02-17T19:03:19+00:00" }, { "name": "AmazonEventBridgeApiDestinationsServiceRolePolicy", "version": "v1", "arn": "arn:aws:iam::aws:policy/aws-service-role/AmazonEventBridgeApiDestinationsServiceRolePolicy", "date": "2021-02-11T20:52:05+00:00" } ]
追加後の出力イメージが以下です。
[ { "index": 0, "value": { "name": "AWSStorageGatewayServiceRolePolicy", "version": "v1", "arn": "arn:aws:iam::aws:policy/aws-service-role/AWSStorageGatewayServiceRolePolicy", "date": "2021-02-17T19:03:19+00:00" } }, { "index": 1, "value": { "name": "AmazonEventBridgeApiDestinationsServiceRolePolicy", "version": "v1", "arn": "arn:aws:iam::aws:policy/aws-service-role/AmazonEventBridgeApiDestinationsServiceRolePolicy", "date": "2021-02-11T20:52:05+00:00" } } ]
後続の手順で、任意の番号のポリシーを選択することになります。フィルタリング条件によっては数が多くなるため、視認性が高くなるようこの処理を入れました。
3. 特定のポリシーの前後のバージョン取得
ここでは変数INDEX
のみ、明示的に指定します。
# | 変数名 | 値の指定 | 概要 |
---|---|---|---|
4 | INDEX | 要 | 手順2.の結果のうち、詳細を確認したい番号を指定 |
5 | ARN | 不要 | 指定した番号のポリシー ARN を取得 |
6 | VER | 不要 | 指定した番号のポリシーのバージョンを取得 |
7 | BEFORE | 不要 | 指定した番号のポリシーの一世代前の内容を取得 |
8 | AFTER | 不要 | 指定した番号のポリシーのデフォルトバージョンの内容を取得 |
以下を実行することで、特定のポリシーの前後の内容が変数に格納されます。
INDEX=3 ARN=`echo $RESULT | jq -r '.['$INDEX'].value.arn'` VER=`echo $RESULT | jq -r '.['$INDEX'].value.version' | tr -d "v"` BEFORE=`aws iam get-policy-version --policy-arn $ARN --version-id v$((VER-1)) --query PolicyVersion.Document` AFTER=`aws iam get-policy-version --policy-arn $ARN --version-id v$VER --query PolicyVersion.Document`
ここでは以下の AWS コマンドを使用しています。
いくつか注意点があるので補足します。
今回は「デフォルトバージョン = 最新バージョン」とみなしている
管理ポリシーではバージョン管理ができます。つまり、過去のバージョンに戻ることができます。
例えばカスタマー管理ポリシーで 、5つのバージョンを作った(ポリシーに変更を加えた)のち、3番目のバージョンに戻したい、となったとします。その場合、3番目をデフォルトバージョンとして指定することで、その内容に巻き戻すことができます。
AWS 管理ポリシーでも上記のように「巻き戻る」ことがあるのかは定かではありませんが、今回はそういったケースは考慮していません。手順2. で確認したDefaultVersionId
が、そのまま最新バージョンの ID であるとみなして手順を整備しています。
厳密に確認したい場合には、以下のコマンドも組み込んでご確認ください。
バージョン v1 のポリシーは指定できない
今回やりたいことが「変更前後の差分を比較する」ことであることから自明かもしれませんが、version(DefaultVersionId
)がv1
のポリシーは指定できません。
厳密に言えば、指定した際にはエラーが発生し、それを回避するようなコマンドにしていません。
バージョンがv1
のポリシーは作成日時(CreateDate
)と更新日時(UpdateDate
)が同一であり、つまりは新規作成されたポリシーであることを表します。
ここまで例として挙げてきたポリシーは残念ながらすべてv1
なので、実際には指定できません。(なぜそんなポリシーを例に挙げたのか、少し前の自分の気持ちがわかりません。疲れてたんですかね。)
4. diff る
いよいよ最後のステップです。
今回は以下のポリシーに対して手順3. を実行し、変数BEFORE
とAFTER
にそれぞれポリシーの内訳が格納済みとします。
AWSCodeStar用のサービスロールが使用するポリシーです。
[ { "index": 0, "value": { "name": "AWSCodeStarServiceRole", "version": "v10", "arn": "arn:aws:iam::aws:policy/service-role/AWSCodeStarServiceRole", "date": "2021-02-15T22:25:37+00:00" } } ]
diff <(echo $BEFORE) <(echo $AFTER)
で、差分比較ができます。
diff の形式にはいくつか種類があるので、何パターンか試してみます。
- デフォルト形式
- unified形式
- context形式
- side-by-side形式
詳細は以下を参照してください。
デフォルト形式
特にオプションを指定せず実行します。
% diff <(echo $BEFORE) <(echo $AFTER) 184a185,204 > }, > { > "Sid": "ProjectCodeStarConnections", > "Effect": "Allow", > "Action": [ > "codestar-connections:UseConnection", > "codestar-connections:GetConnection" > ], > "Resource": "*" > }, > { > "Sid": "ProjectCodeStarConnectionsPassConnections", > "Effect": "Allow", > "Action": "codestar-connections:PassConnection", > "Resource": "*", > "Condition": { > "ForAnyValue:StringEqualsIfExists": { > "codestar-connections:PassedToService": "codepipeline.amazonaws.com" > } > }
AFTERの方で追加された部分が確認できます。
unified形式
-u
を付与して実行します。
% diff <(echo $BEFORE) <(echo $AFTER) -u --- /dev/fd/11 2021-03-04 01:22:35.000000000 +0900 +++ /dev/fd/12 2021-03-04 01:22:35.000000000 +0900 @@ -182,6 +182,26 @@ "Resource": [ "*" ] + }, + { + "Sid": "ProjectCodeStarConnections", + "Effect": "Allow", + "Action": [ + "codestar-connections:UseConnection", + "codestar-connections:GetConnection" + ], + "Resource": "*" + }, + { + "Sid": "ProjectCodeStarConnectionsPassConnections", + "Effect": "Allow", + "Action": "codestar-connections:PassConnection", + "Resource": "*", + "Condition": { + "ForAnyValue:StringEqualsIfExists": { + "codestar-connections:PassedToService": "codepipeline.amazonaws.com" + } + } } ] }
見やすくなったようなそうでもないような……はい。差分の前後の行も表示してくれています。
context形式
-c
を付与して実行します。
% diff <(echo $BEFORE) <(echo $AFTER) -c *** /dev/fd/11 2021-03-04 01:24:43.000000000 +0900 --- /dev/fd/12 2021-03-04 01:24:43.000000000 +0900 *************** *** 182,187 **** --- 182,207 ---- "Resource": [ "*" ] + }, + { + "Sid": "ProjectCodeStarConnections", + "Effect": "Allow", + "Action": [ + "codestar-connections:UseConnection", + "codestar-connections:GetConnection" + ], + "Resource": "*" + }, + { + "Sid": "ProjectCodeStarConnectionsPassConnections", + "Effect": "Allow", + "Action": "codestar-connections:PassConnection", + "Resource": "*", + "Condition": { + "ForAnyValue:StringEqualsIfExists": { + "codestar-connections:PassedToService": "codepipeline.amazonaws.com" + } + } } ] }
正直 unified形式との違いがあまり分かっていません。
side-by-side形式
-y
を付与した上で、必要に応じて以下を指定します。
--left-column
:共通する部分は左列にのみ表示-W
:一列あたりの文字数を指定
結果をブログに添付したところ表示が崩れてしまったのですが、イメージとしては以下のような形で二列で表示されます。
% diff <(echo $BEFORE) <(echo $AFTER) -y --left-column -W 190 { ( "Version": "2012-10-17", ( "Statement": [ ( { ( "Sid": "ProjectEventRules", ( "Effect": "Allow", ( "Action": [ ( "events:PutTargets", ( "events:RemoveTargets", ( "events:PutRule", ( "events:DeleteRule", ( "events:DescribeRule" ( ], ( "Resource": [ ( "arn:aws:events:*:*:rule/awscodestar-*" ( ] ( }, ( { ( "Sid": "ProjectStack", ( "Effect": "Allow", ( "Action": [ ( "cloudformation:*Stack*", ( "cloudformation:CreateChangeSet", ( "cloudformation:ExecuteChangeSet", ( "cloudformation:DeleteChangeSet", ( "cloudformation:GetTemplate" ( ], ( "Resource": [ ( "arn:aws:cloudformation:*:*:stack/awscodestar-*", ( "arn:aws:cloudformation:*:*:stack/awseb-*", ( "arn:aws:cloudformation:*:*:stack/aws-cloud9-*", ( "arn:aws:cloudformation:*:aws:transform/CodeStar*" ( ] ( }, ( { ( "Sid": "ProjectStackTemplate", ( "Effect": "Allow", ( "Action": [ ( "cloudformation:GetTemplateSummary", ( "cloudformation:DescribeChangeSet" ( ], ( "Resource": "*" ( }, ( { ( "Sid": "ProjectQuickstarts", ( "Effect": "Allow", ( "Action": [ ( "s3:GetObject" ( ], ( "Resource": [ ( "arn:aws:s3:::awscodestar-*/*" ( ] ( }, ( { ( "Sid": "ProjectS3Buckets", ( "Effect": "Allow", ( "Action": [ ( "s3:*" ( ], ( "Resource": [ ( "arn:aws:s3:::aws-codestar-*", ( "arn:aws:s3:::aws-codestar-*/*", ( "arn:aws:s3:::elasticbeanstalk-*", ( "arn:aws:s3:::elasticbeanstalk-*/*" ( ] ( }, ( { ( "Sid": "ProjectServices", ( "Effect": "Allow", ( "Action": [ ( "codestar:*", ( "codecommit:*", ( "codepipeline:*", ( "codedeploy:*", ( "codebuild:*", ( "ec2:RunInstances", ( "autoscaling:*", ( "cloudwatch:Put*", ( "ec2:*", ( "elasticbeanstalk:*", ( "elasticloadbalancing:*", ( "iam:ListRoles", ( "logs:*", ( "sns:*", ( "cloud9:CreateEnvironmentEC2", ( "cloud9:DeleteEnvironment", ( "cloud9:DescribeEnvironment*", ( "cloud9:ListEnvironments" ( ], ( "Resource": "*" ( }, ( { ( "Sid": "ProjectWorkerRoles", ( "Effect": "Allow", ( "Action": [ ( "iam:AttachRolePolicy", ( "iam:CreateRole", ( "iam:DeleteRole", ( "iam:DeleteRolePolicy", ( "iam:DetachRolePolicy", ( "iam:GetRole", ( "iam:PassRole", ( "iam:GetRolePolicy", ( "iam:PutRolePolicy", ( "iam:SetDefaultPolicyVersion", ( "iam:CreatePolicy", ( "iam:DeletePolicy", ( "iam:AddRoleToInstanceProfile", ( "iam:CreateInstanceProfile", ( "iam:DeleteInstanceProfile", ( "iam:RemoveRoleFromInstanceProfile" ( ], ( "Resource": [ ( "arn:aws:iam::*:role/CodeStarWorker*", ( "arn:aws:iam::*:policy/CodeStarWorker*", ( "arn:aws:iam::*:instance-profile/awscodestar-*" ( ] ( }, ( { ( "Sid": "ProjectTeamMembers", ( "Effect": "Allow", ( "Action": [ ( "iam:AttachUserPolicy", ( "iam:DetachUserPolicy" ( ], ( "Resource": "*", ( "Condition": { ( "ArnEquals": { ( "iam:PolicyArn": [ ( "arn:aws:iam::*:policy/CodeStar_*" ( ] ( } ( } ( }, ( { ( "Sid": "ProjectRoles", ( "Effect": "Allow", ( "Action": [ ( "iam:CreatePolicy", ( "iam:DeletePolicy", ( "iam:CreatePolicyVersion", ( "iam:DeletePolicyVersion", ( "iam:ListEntitiesForPolicy", ( "iam:ListPolicyVersions", ( "iam:GetPolicy", ( "iam:GetPolicyVersion" ( ], ( "Resource": [ ( "arn:aws:iam::*:policy/CodeStar_*" ( ] ( }, ( { ( "Sid": "InspectServiceRole", ( "Effect": "Allow", ( "Action": [ ( "iam:ListAttachedRolePolicies" ( ], ( "Resource": [ ( "arn:aws:iam::*:role/aws-codestar-service-role", ( "arn:aws:iam::*:role/service-role/aws-codestar-service-role" ( ] ( }, ( { ( "Sid": "IAMLinkRole", ( "Effect": "Allow", ( "Action": [ ( "iam:CreateServiceLinkedRole" ( ], ( "Resource": "*", ( "Condition": { ( "StringEquals": { ( "iam:AWSServiceName": "cloud9.amazonaws.com" ( } ( } ( }, ( { ( "Sid": "DescribeConfigRuleForARN", ( "Effect": "Allow", ( "Action": [ ( "config:DescribeConfigRules" ( ], ( "Resource": [ ( "*" ( ] ( > }, > { > "Sid": "ProjectCodeStarConnections", > "Effect": "Allow", > "Action": [ > "codestar-connections:UseConnection", > "codestar-connections:GetConnection" > ], > "Resource": "*" > }, > { > "Sid": "ProjectCodeStarConnectionsPassConnections", > "Effect": "Allow", > "Action": "codestar-connections:PassConnection", > "Resource": "*", > "Condition": { > "ForAnyValue:StringEqualsIfExists": { > "codestar-connections:PassedToService": "codepipeline.amazonaws.com" > } > } } ( ] ( } (
あまり diff の読み方に精通していない自分としては、この形式が一番好きです。
ただ、差分だけでなく全体が出力されるので、ポリシーが長いと読みづらいかもしれません。
ニヤニヤできましたか?
ここまでいくつかの形式で差分を確認してきましたが、ニヤニヤできたでしょうか。
実は、私はあまりできませんでした。
差分を眺めること自体は楽しいのですが、今回取り上げた AWSCodeStar のことをほとんど分かっていないので、ポリシーの内容についての理解が追いついていません。本当の意味でニヤニヤするためには、サービスについての知識を深めるアプローチも必要そうです。がんばります。
終わりに
ポリシーの差分を眺めてニヤニヤする手順を整理してみました。
当初はすべてスクリプト化しようかと考えたのですが、条件を変えて何度もばちばち叩きたいだろうな、ということを考えるとそこまでうまい具合に落とし込めなく、このような形となりました。
今回の主たるモチベーションは「 AWS アップデートの予兆を観測するために AWS サービスロール用ポリシーの差分を確認する」というものですが、条件を変えれば様々なケースに対応できるかと思います。
要件に応じてニヤニヤしていただければ幸いです。
以上、千葉(幸)がお送りしました。